#include "server.h"
#include "http.h"
#include <string>

//#define DEBUG

int parseRequest(int i_Index, char* o_Path, enum Request* o_RequestType)
{
	char* buffer = sockets[i_Index].buffer;
	char temp[BUFFER_SIZE] = {0};
	char* currentToken;

	if (o_RequestType == NULL)
		return 0;

	if (!isMessageComplete(i_Index))
		return 0;	

#ifdef DEBUG
	cout << "isMessageComplete returned true ";
#endif

	strncpy(temp, buffer, BUFFER_SIZE);
	if (temp == NULL)
		return 0;

	currentToken = strtok(temp, " ");
	if (currentToken == NULL)
		return 0;
	
	toUpper(currentToken);

#ifdef DEBUG
	cout << currentToken << endl;
#endif

	if (strncmp(currentToken, GET_REQ, BUFFER_SIZE) == 0)
	{
		*o_RequestType = HTTP_GET;
	}
	else if (strncmp(currentToken, HEAD_REQ, BUFFER_SIZE) == 0)
	{
		*o_RequestType = HTTP_HEAD;
	}
	else if (strncmp(currentToken, PUT_REQ, BUFFER_SIZE) == 0)
	{
		*o_RequestType = HTTP_PUT;
	}
	else if (strncmp(currentToken, DELETE_REQ, BUFFER_SIZE) == 0)
	{
		*o_RequestType = HTTP_DELETE;
	}
	else if (strncmp(currentToken, TRACE_REQ, BUFFER_SIZE) == 0)
	{
		*o_RequestType = HTTP_TRACE;
	}
	else 
	{
		*o_RequestType = HTTP_NOT_IMPLEMENTED;
	}

	currentToken = strtok(NULL, " ");
	if (currentToken == NULL)
		return 0;
	strcpy(o_Path, currentToken);

	currentToken = strtok(NULL, "\r\n");
	if (currentToken == NULL || strcmp(currentToken, "HTTP/1.1") != 0)
		return 0;
	
	return 1;
}

int isMessageComplete(int i_Index)
{
	int headerLength;
	if ((headerLength = getMessageBodyOffset(i_Index)) == -1 )
		return 0;
	
	int contentLength;
	if ((contentLength = retrieveContentLength(i_Index)) == -1)
		return 1;

	if (sockets[i_Index].len ==  headerLength + contentLength)
		return 1;

	return 0;
}

int HandleRequest(int i_Index)
{
	char path[BUFFER_SIZE];
	enum Request request;
	bool v_includeBody = true;

	if (parseRequest(i_Index, path, &request) == 0)
	{
		return 0;
	}

	switch(request)
	{
	case HTTP_GET:
		getResponse(i_Index, path, v_includeBody);
		break;
	case HTTP_HEAD:
		getResponse(i_Index, path, !v_includeBody);
		break;
	case HTTP_PUT:
		putResponse(i_Index, path);
		break;
	case HTTP_DELETE:
		deleteResponse(i_Index, path);
		break;
	case HTTP_TRACE:
		traceResponse(i_Index);
		break;
	case HTTP_NOT_IMPLEMENTED:
		notImplemented(i_Index);
		break;
	default:
		notImplemented(i_Index); 
		break;
	}

	return 1;
}

void notImplemented(int i_Index)
{
	HeaderMaker(NOT_IMPLEMENTED, 0, sockets[i_Index].buffer, BUFFER_SIZE);
}

void traceResponse(int i_Index)
{
	char originalMessage[BUFFER_SIZE] = {0};
	int originalLength;

	strncpy(originalMessage, sockets[i_Index].buffer, BUFFER_SIZE);
	originalLength = strlen(originalMessage);
	HeaderMaker(OK, originalLength, sockets[i_Index].buffer, BUFFER_SIZE);
	strncat(sockets[i_Index].buffer, originalMessage, BUFFER_SIZE);
}

void toUpper(char* str)
{
	int len = strlen(str);

	for(int i = 0; i < len; i++)
	{
		str[i] = (char)toupper(str[i]);
	}
}

void HeaderMaker(enum Response i_ResponseType, int i_ContentLength, char* o_Buffer, int i_BufferSize)
{
	string str;
	char buffer[100];

	str = "HTTP/1.1";
	str.append(" ");
	switch(i_ResponseType)
	{
	case OK:
		str.append("200 OK");
		break;
	case CREATED:
		str.append("201 Created");
		break;
	case NOT_FOUND:
		str.append("404 Not Found");
		break;
	case NOT_IMPLEMENTED:
		str.append("501 Not Implemeted");
		break;
	case NO_CONTENT:
		str.append("204 No Content");
		break;
	default:
		str.append("500 Internal Server Error");
		break;
	}

	str.append(CRLF);

	if (i_ContentLength != 0)
	{
		str.append("Content-Length: ");
		str.append(itoa(i_ContentLength, buffer, 10));
		str.append(CRLF);
		str.append("Content-Type: text/html");
		str.append(CRLF);
	}

	str.append("Connection: keep-alive");
	str.append(CRLF);
	str.append(CRLF);

	strncpy(o_Buffer, str.c_str(), i_BufferSize);
}

void getFilePath(char* i_Path, char* o_Path)
{
	string pathBuilder;
	char* httpPrefix = "HTTP:\\";
	char temp [BUFFER_SIZE];
	char* currentToken;
	int i = 0;

	strncpy(temp, i_Path, BUFFER_SIZE);
	if(strncmp(temp, httpPrefix, 7) == 0) // skip HTTP://
		i += 7; 
	while(temp[i] != '/') //skip the domain
		i++;

	currentToken = strtok(temp,"/");
	if(currentToken == NULL)
	{
		o_Path = NULL;
		return;
	}

	do
	{
		pathBuilder.append(currentToken);
		pathBuilder.append("\\");
		currentToken = strtok(NULL,"/");
	} while(currentToken != NULL);
	
	strcpy(o_Path, pathBuilder.c_str());
	o_Path[strlen(o_Path)-1] = NULL;
}

void getResponse(int i_Index, char* i_Path, bool i_IncludeBody)
{
	//0. open file in path
	char pathToFile[BUFFER_SIZE];
	FILE* file;
	
	getFilePath(i_Path, pathToFile);

	if ((file = fopen(pathToFile,"r")) == NULL)
	{
		char notFoundContent[100] = {0};
		strcpy(notFoundContent, "<html><body><p>404 Not Found</p></body></html>");
		HeaderMaker(NOT_FOUND, strlen(notFoundContent), sockets[i_Index].buffer, BUFFER_SIZE);
		memcpy(sockets[i_Index].buffer + strlen(sockets[i_Index].buffer), notFoundContent, strlen(notFoundContent));
		return;
	}

	if (!i_IncludeBody)
	{
		HeaderMaker(OK, 0, sockets[i_Index].buffer, BUFFER_SIZE);
		return;		
	}

	//1. read the file to the pointer
	fseek(file, 0, SEEK_END);
	sockets[i_Index].fileSize = ftell(file);
	fseek(file, 0, SEEK_SET);

	sockets[i_Index].file = (char*) calloc(sizeof(char) , sockets[i_Index].fileSize);
	fread(sockets[i_Index].file, sizeof(char), sockets[i_Index].fileSize, file);
	fclose(file);
	HeaderMaker(OK, sockets[i_Index].fileSize, sockets[i_Index].buffer, BUFFER_SIZE);
}

void deleteResponse(int i_Index, char* i_Path)
{
	char pathToFile[BUFFER_SIZE];
	getFilePath(i_Path, pathToFile);

	if (remove(pathToFile) == 0)
	{	
		HeaderMaker(OK, 0,sockets[i_Index].buffer, BUFFER_SIZE);
	}
	else
	{
		// failed to delete
		HeaderMaker(NO_CONTENT, 0, sockets[i_Index].buffer, BUFFER_SIZE);
	}
}

void putResponse(int i_Index, char* i_Path)
{
	char* fileContent = retrieveBody(i_Index);
	int contentLength = retrieveContentLength(i_Index);
	
	FILE* newFile;
	char pathToFile[BUFFER_SIZE];
	getFilePath(i_Path, pathToFile);

	if ((newFile = fopen(pathToFile, "rb")) == NULL)
	{
		//file doesenesense't exist, no permissions, etc. 
		HeaderMaker(CREATED, 0, sockets[i_Index].buffer, BUFFER_SIZE);
	}
	else
	{
		//file exists
		HeaderMaker(OK, 0, sockets[i_Index].buffer, BUFFER_SIZE);
		fclose(newFile);
	}
	
	if ((fileContent == NULL) || (newFile = fopen(pathToFile, "wb")) == NULL)
	{
		HeaderMaker(INTERNAL_SERVER_ERROR, 0, sockets[i_Index].buffer, BUFFER_SIZE);
		return;		
	}

	if	(fwrite(fileContent, sizeof(char), contentLength, newFile) != contentLength)
	{
		//didn't write all the bytes, delete the file and respond with an error.
		fclose(newFile);
		remove(pathToFile);
		HeaderMaker(INTERNAL_SERVER_ERROR, 0, sockets[i_Index].buffer, BUFFER_SIZE);
		return;		
	}

	fclose(newFile);
}

char* retrieveBody(int i_Index)
{
#ifdef DEBUG
	cout << "entering retrieveBody" << endl;
#endif
	char* fileContent; 
	int fileLen = retrieveContentLength(i_Index);
	int fileOffset = getMessageBodyOffset(i_Index);

#ifdef DEBUG
	cout << "retrieveBody: fileLen == " << fileLen << ", fileOffset == " << fileOffset << endl;
#endif
	
	if (fileLen == -1 || fileOffset == -1) 
	{
		return NULL;
	}

	fileContent = (char*) calloc(sizeof(char), fileLen);
	memcpy(fileContent, sockets[i_Index].buffer + fileOffset, fileLen); 
	
	return fileContent;
}

int retrieveContentLength(int i_Index)
{
	char tempBuf[BUFFER_SIZE];
	char* currentToken = NULL;
	strncpy(tempBuf, sockets[i_Index].buffer, BUFFER_SIZE);
	
	currentToken = strtok(tempBuf, "\r\n");

	while (currentToken != NULL)
	{
		if ( strncmp("Content-Length:", currentToken, 15) == 0)
		{
			return atoi(currentToken + 15);
		}
		currentToken = strtok(NULL, "\r\n");
	}

	return -1;
}

int getMessageBodyOffset(int i_Index)
{
	char* buffer = sockets[i_Index].buffer;
	int i = 0;
	char* sequence = "\r\n\r\n";

	while ( i < BUFFER_SIZE - 4)
	{
		if (buffer[i] == '\r' &&
			buffer[i + 1] == '\n' &&
			buffer[i + 2] == '\r' &&
			buffer[i + 3] == '\n')
		{
			return i + 4;
		}
		i++;
	}
	
	return -1;
}